#include <AST/expr/var/ArrayPtrAST.hpp>
#include <global.hpp>
#include <AST/expr/literal/IntAST.hpp>

// 引用一维数组
ArrayPtr::ArrayPtr(string name, Expr *index)
    : Var(std::move(name)), elementType(nullptr), length(0), index(index) {
    assert(index != nullptr && "引用 ArrayPtr 时必须有index");
}

// 引用高维数组
ArrayPtr::ArrayPtr(ArrayPtr *hdArr, Expr *index)
    : Var(hdArr->getName()),
      elementType(hdArr->type),
      length(0),
      index(index),
      hdArr(hdArr) {
    assert(index != nullptr && "引用 ArrayPtr 时必须有index");
}

// 定义一个数组，初始化为已有的数组（a=b）
ArrayPtr::ArrayPtr(
    string name,
    bool isDefinition,
    bool isConst,
    Expr *initExpr,
    bool isNullable,
    Type *arrayOrPtrType)
    // Expr *lengthConstexpr)
    : Var(
        std::move(name),
        isDefinition,
        isConst,
        isNullable,
        initExpr,
        arrayOrPtrType) {
    if (arrayOrPtrType != nullptr) {
        if (arrayOrPtrType->isArrayTy()) {
            length = arrayOrPtrType->getArrayNumElements();
            elementType = arrayOrPtrType->getArrayElementType();
        } else {
            elementType = arrayOrPtrType->getPointerTo();
        }
    }
    // XXX 类型未指定，则自动推导类型
    // 延迟到 codegen 阶段，才能知道 initVector 的类型
}

// 定义一个数组，初始化为新的数组列表（a=[1,2,3]）
ArrayPtr::ArrayPtr(
    string name,
    bool isDefinition,
    bool isConst,
    InitVector initVector,
    bool isNullable,
    Type *arrayOrPtrType)
    // Expr *lengthConstexpr)
    : Var(
        // BUG initExpr
        std::move(name),
        isDefinition,
        isConst,
        isNullable,
        new Int(0), // 搪塞一个initExpr，随后删除
        arrayOrPtrType),
      initVector(std::move(initVector)) {
    delete initExpr;
    initExpr = nullptr; // 随后删除initExpr，改用initVector
    if (arrayOrPtrType != nullptr) {
        if (arrayOrPtrType->isArrayTy()) {
            length = arrayOrPtrType->getArrayNumElements();
            elementType = arrayOrPtrType->getArrayElementType();
        } else {
            elementType = arrayOrPtrType->getPointerTo();
        }
    }
    if (length != 0) {
        // 有指定的长度
        assert(
            length >= this->initVector.size()
            && "array length is too small to contain initializer");
        // XXX 留意 initVector 比定义长度短的情况
    } else {
        // 无指定长度，必须有initVector
        assert(
            this->initVector.size() > 0
            && "array without specified length must has a non-empty initializer");
        length = this->initVector.size();
    }
    assert(
        !(length == 0 && elementType == nullptr)
        && "Impossible to infer the element type with empty initializer");
    // XXX 类型未指定，则自动推导类型
    // 延迟到 codegen 阶段，才能知道 initVector 的类型
}

// TODO
llvm::Value *ArrayPtr::codegen() {
    // 情况：数组的引用
    if (!isDefinition) return valCodegen();
    // 情况：数组定义
    return defCodegen();
}

llvm::Value *ArrayPtr::refCodegen() {
    Value *arrV = nullptr;
    if (hdArr != nullptr) {
        // 情况：对更高维数组解引
        arrV = hdArr->valCodegen();
        // 更新type, elementType, length
        // 检查类型是否正确
        print_v(arrV);
        print_t(arrV->getType());
        updateTypeByPtr(arrV);
    } else {
        // 情况：当前是最高维数组
        arrV = theScope.findValue(name);
        if (arrV == nullptr)
            return logError("ArrayPtr::codegen: unknown array name: " + name);
        // 更新type, elementType, length
        // 检查类型是否正确
        updateTypeByPtr(arrV);
        print_v(arrV);
        print_t(arrV->getType());
        print_t(type);
        // 读取值：整个数组
        // Value *arrV = builder.CreateLoad(type, arrV, name);
        // Value *arrV = builder.CreateGEP(arrV, Int(0).codegen(), "getArray");
    }

    // 情况：数组的整体引用（指向数组的指针）
    if (index == nullptr) return arrV;

    // 情况：数组的下标引用
    Value *idxV = index->codegen();
    if (idxV == nullptr)
        return logError("ArrayPtr::codegen: index codegen failed");

    // 创建 GetElementPtr
    llvm::SmallVector<Value *, 2> idxs{Int(0).codegen(), idxV};
    print_t(type);
    print_t(arrV->getType());
    Value *elementAddr =
        builder.CreateGEP(type, arrV, idxs, "getArrayElementPtr");

    using Twine = llvm::Twine;
    Twine hintName = Twine(name) + Twine(".get");

    print_v(elementAddr);
    print_t(elementAddr->getType());

    return elementAddr;
}

llvm::AllocaInst *ArrayPtr::createEntryAlloca(Function *fun) {
    if (type == nullptr) {
        logError("ArrayPtr::CreateEntryAlloca: var has no known type");
        return nullptr;
    }
    llvm::IRBuilder<> tmpBuilder(
        &fun->getEntryBlock(), fun->getEntryBlock().begin());
    return tmpBuilder.CreateAlloca(type, 0, name);
}

llvm::AllocaInst *ArrayPtr::createEntryArrayAlloca(Function *fun) {
    if (type == nullptr) {
        logError("ArrayPtr::createEntryArrayAlloca: var has no known type");
        return nullptr;
    }
    llvm::IRBuilder<> tmpBuilder(
        &fun->getEntryBlock(), fun->getEntryBlock().begin());
    return tmpBuilder.CreateAlloca(type, 0, "arrContainer");
    // type->getPointerElementType(), 0, "arrContainer");
}

llvm::AllocaInst *ArrayPtr::createAlloca() {
    if (type == nullptr) {
        logError("ArrayPtr::createAlloca: var has no known type");
        return nullptr;
    }
    return builder.CreateAlloca(type, 0, name);
}

// 若无index，则应该执行Var::valCodegen
llvm::Value *ArrayPtr::valCodegen() {
    Value *arrV = nullptr;
    if (hdArr != nullptr) {
        // 情况：对更高维数组解引
        arrV = hdArr->valCodegen();
        // 更新type, elementType, length
        // 检查类型是否正确
        print_v(arrV);
        print_t(arrV->getType());
        updateTypeByPtr(arrV);
    } else {
        // 情况：当前是最高维数组
        arrV = theScope.findValue(name);
        if (arrV == nullptr)
            return logError("ArrayPtr::codegen: unknown array name: " + name);
        // 更新type, elementType, length
        // 检查类型是否正确
        updateTypeByPtr(arrV);
        print_v(arrV);
        print_t(arrV->getType());
        print_t(type);
    }

    // 情况：数组的整体引用（指向数组的指针）
    if (index == nullptr) return arrV;

    // 情况：数组的下标引用
    Value *idxV = index->codegen();
    if (idxV == nullptr)
        return logError("ArrayPtr::codegen: index codegen failed");

    // 创建 GetElementPtr
    llvm::SmallVector<Value *, 2> idxs{Int(0).codegen(), idxV};
    Value *elementAddr = nullptr;
    elementAddr = builder.CreateGEP(type, arrV, idxs, "getArrayElementPtr");

    using Twine = llvm::Twine;
    Twine hintName = Twine(name) + Twine(".get");
    return elementAddr->getType()->getPointerElementType()->isArrayTy()
               ? elementAddr
               : builder.CreateLoad(elementType, elementAddr, hintName);
}

llvm::Value *ArrayPtr::defCodegen() {
    Function *fun = builder.GetInsertBlock()->getParent();

    Value *initVal = nullptr;
    if (initExpr != nullptr) {
        // 看有没有初始值
        initVal = initExpr->codegen();
        // 检查类型是否吻合
        if (type != nullptr && type != initVal->getType())
            return logError(
                "ArrayPtr::codegen: The declared and defined types do not match");
        // 敲定类型
        if (type == nullptr) updateTypeByVal(initVal);
    } else {
        // 不是引用已有数组，则需要新开数组空间
        // 有initVector，但长度可能为0
        Value *element0 = nullptr;
        Type *type0 = nullptr;
        if (initVector.size() != 0) {
            // 若 initVector 非空，则推断类型
            element0 = initVector[0]->codegen();
            type0 = element0->getType();
            if (elementType == nullptr) elementType = type0;
            else if (elementType != type0)
                return logError("ArrayPtr::defCodegen: type of element0"
                                " is different from decleard type");
        } else {
            // 若 initVector 空，赋值0
            type0 = elementType;
            element0 = Constant::getNullValue(type0);
        }
        // length 肯定已知
        // 更新Var::type
        Type *varType =
            // llvm::ArrayType::get(elementType, length)->getPointerTo();
            llvm::ArrayType::get(elementType, length);
        if (type == nullptr) type = varType;
        else {
            print_t(type);
            print_t(varType);
            assert(type == varType);
        }

        // 申请多个元素的栈空间
        initVal = createEntryArrayAlloca(fun);
        print_v(initVal);
        print_t(initVal->getType());

        if (!initVector.empty()) {
            // 初始化每个元素
            /*
            llvm::SmallVector<Value *, 2> idxs = {
                Int(0).codegen(), Int(0).codegen()};
            */
            Value *tmpPtr = builder.CreateGEP(
                type, initVal, Int(0).codegen(), "arrInitPtr");
            // 首先初始化第一个元素
            builder.CreateStore(element0, tmpPtr, false);
            // 初始化剩余元素，检查类型一致性
            for (unsigned int i = 1; i < initVector.size(); ++i) {
                Value *v = initVector[i]->codegen();
                if (v->getType() != elementType)
                    return logError(
                        "ArrayPtr::defCodegen: types of elements are not"
                        " identical");
                // idxs[1] = Int(i).codegen();
                tmpPtr = builder.CreateGEP(
                    type, initVal, Int(i).codegen(), "arrInitPtr");
                builder.CreateStore(v, tmpPtr, false);
            }
        }
    }

    // 申请栈空间
    // AllocaInst *alloc = createEntryAlloca(fun);
    // 储存初始值
    // builder.CreateStore(initVal, alloc);
    // 添加到符号表
    // if (!theScope.insertValue(name, alloc)) {
    if (!theScope.insertValue(name, initVal)) {
        return logError("ArrayPtr::defCodegen: redefinition found: " + name);
    }

    // 定义语句返回Void
    return UndefValue::get(BasicType::VoidTy);
}

llvm::Type *ArrayPtr::updateTypeByVal(const Value *val) {
    assert(val->getType()->getPointerElementType()->isArrayTy());
    Var::updateTypeByVal(val);
    elementType = type->getPointerElementType()->getArrayElementType();
    length = type->getPointerElementType()->getArrayNumElements();
    return type;
}

llvm::Type *ArrayPtr::updateTypeByPtr(const Value *ptr) {
    print_v(ptr);
    Type *deRefType = ptr->getType()->getPointerElementType();
    Var::updateTypeByPtr(ptr);
    // 传参时可能没有数组长度信息，从而不是ArrayTy
    // assert(ptr->getType()
    //            ->getPointerElementType()
    //            ->getPointerElementType()
    //            ->isArrayTy());
    if (deRefType->isArrayTy()) {
        elementType = deRefType->getArrayElementType();
        length = deRefType->getArrayNumElements();
    } else {
        elementType = deRefType->getPointerElementType();
        length = 0;
    }
    print_t(type);
    return type;
}
